package majunimpl

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"errors"
	"net/http"
	"strconv"
	"time"

	"github.com/astaxie/beego"
	"github.com/opensourceways/server-common-lib/utils"

	"cvevulner/cve-ddd/domain"
)

const (
	userAgent      = "Apifox/1.0.0 (https://apifox.com)"
	urlGetPlanDate = "/gateway/openlibing-vulnerability/" +
		"openlibing-vulnerability-view/admin/ci-portal/ci-admin/cve/getTime/details"
	urlReleaseVersion  = "/gateway/openlibing-platform-release/publish/externalInterface/queryReleaseVersion"
	urlCollectCallback = "/gateway/openlibing-platform-release/publish/externalInterface/saveCveCallbackResult"
)

type config struct {
	AppId     string
	SecretKey string
}

func NewMajunImpl() *majunImpl {
	return &majunImpl{
		cfg: config{
			AppId:     beego.AppConfig.String("majun::app_id"),
			SecretKey: beego.AppConfig.String("majun::secret_key"),
		},
		client: utils.NewHttpClient(3),
	}
}

type majunImpl struct {
	cfg    config
	client utils.HttpClient
}

type releaseResponse struct {
	Code    int      `json:"code"`
	Message string   `json:"message"`
	Result  []string `json:"result"`
}

func (impl *majunImpl) GetReleasedBranch() ([]string, error) {
	url := beego.AppConfig.String("majun::host") + urlReleaseVersion
	req, err := impl.generateRequest(url, nil)
	if err != nil {
		return nil, err
	}

	var v releaseResponse
	_, err = impl.client.ForwardTo(req, &v)
	if err != nil {
		return nil, err
	}

	if v.Code != http.StatusOK {
		return nil, errors.New(v.Message)
	}

	return v.Result, nil
}

type callbackBody struct {
	Code int                  `json:"code"`
	Id   string               `json:"id"`
	Msg  string               `json:"msg"`
	Data []domain.CallbackDTO `json:"data"`
}

func (impl *majunImpl) CollectCallback(id, data string) error {
	var dataDTO []domain.CallbackDTO
	err := json.Unmarshal([]byte(data), &dataDTO)
	if err != nil {
		return err
	}

	body := callbackBody{
		Code: 200,
		Id:   id,
		Msg:  "success",
		Data: dataDTO,
	}

	payload, err := json.Marshal(body)
	if err != nil {
		return err
	}

	url := beego.AppConfig.String("majun::host") + urlCollectCallback
	req, err := impl.generateRequest(url, payload)
	if err != nil {
		return err
	}

	var v releaseResponse
	_, err = impl.client.ForwardTo(req, &v)
	if err != nil {
		return err
	}

	if v.Code != http.StatusOK {
		return errors.New(v.Message)
	}

	return nil
}

// PlanDataOfMaJun plan data of majun
type PlanDataOfMaJun struct {
	IssueNum  string  `json:"issueId"`
	CveNum    string  `json:"cveNum"`
	CvssScore float64 `json:"cvssScore"`
	BeginTime string  `json:"cveRepairStartTime"`
	EndTime   string  `json:"issuePlanClosedTIme"`
}

// PlanResponse response of majun
type PlanResponse struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	Result  struct {
		Data []PlanDataOfMaJun `json:"data"`
	}
}

// PlanRequest request param
type PlanRequest struct {
	Organization string `json:"organization"`
	PageNum      int    `json:"pageNum"`
	PageSize     int    `json:"pageSize"`
}

func (impl *majunImpl) GetPlanDate(page, pageSize int) ([]PlanDataOfMaJun, error) {
	param := PlanRequest{
		Organization: "openeuler",
		PageNum:      page,
		PageSize:     pageSize,
	}
	payload, err := json.Marshal(param)
	if err != nil {
		return nil, err
	}

	url := beego.AppConfig.String("majun::host") + urlGetPlanDate

	req, err := impl.generateRequest(url, payload)
	if err != nil {
		return nil, err
	}

	var ret PlanResponse
	_, err = impl.client.ForwardTo(req, &ret)

	return ret.Result.Data, err
}

func (impl *majunImpl) generateRequest(url string, body []byte) (req *http.Request, err error) {
	timestamp := strconv.FormatInt(time.Now().UnixMilli(), 10)
	sign := impl.sign(timestamp)

	if len(body) != 0 {
		req, err = http.NewRequest(http.MethodPost, url, bytes.NewBuffer(body))
	} else {
		req, err = http.NewRequest(http.MethodPost, url, nil)
	}
	if err != nil {
		return
	}

	req.Header.Set("timestamp", timestamp)
	req.Header.Set("accountid", impl.cfg.AppId)
	req.Header.Set("sign", sign)
	req.Header.Set("User-Agent", userAgent)
	req.Header.Set("Content-Type", "application/json")

	return
}

func (impl *majunImpl) sign(timestamp string) string {
	msg := impl.cfg.AppId + timestamp

	mac := hmac.New(sha256.New, []byte(impl.cfg.SecretKey))
	_, _ = mac.Write([]byte(msg))

	return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}
